package cn.sciento.boot.api.customize.service.impl;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import cn.sciento.core.exception.CommonException;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import cn.sciento.boot.api.customize.agent.HotSwapClient;
import cn.sciento.boot.api.customize.commons.constant.CustomizeRulePosition;
import cn.sciento.boot.api.customize.commons.repository.ApiCustomizeRepository;
import cn.sciento.boot.api.customize.commons.vo.ApiCustomizePoint;
import cn.sciento.boot.api.customize.commons.vo.ApiCustomizeRule;
import cn.sciento.boot.api.customize.exception.VMNotFoundException;
import cn.sciento.boot.api.customize.generator.CodeGenerator;
import cn.sciento.boot.api.customize.service.ApiCustomizeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultApiCustomizeService extends ApiCustomizeService {
  private static final Logger LOGGER = LoggerFactory.getLogger(DefaultApiCustomizeService.class);
  
  public DefaultApiCustomizeService(ApiCustomizeRepository apiCustomizeRepository, CodeGenerator codeGenerator) {
    super(apiCustomizeRepository, codeGenerator);
  }
  
  protected void applyRule(ApiCustomizePoint point, List<ApiCustomizeRule> rules) {
    ClassPool classPool = new ClassPool(true);
    classPool.appendClassPath((ClassPath)new ClassClassPath(getClass()));
    try {
      String targetClassName = point.getPackageName() + "." + point.getClassName();
      CtClass targetClass = classPool.get(targetClassName);
      CtClass[] params = buildParams(classPool, point);
      CtMethod targetMethod = targetClass.getDeclaredMethod(point.getMethodName(), params);
      LOGGER.info("customize target class is [{}], target method is [{}]", targetClassName, targetMethod.getName());
      Map<String, List<ApiCustomizeRule>> map = (Map<String, List<ApiCustomizeRule>>)rules.stream().collect(Collectors.groupingBy(ApiCustomizeRule::getRulePosition));
      String beforeCode = buildCodeBlock(map.get(CustomizeRulePosition.BEFORE.name()), classPool, targetClass, targetMethod, false);
      String replaceCode = buildCodeBlock(map.get(CustomizeRulePosition.REPLACE.name()), classPool, targetClass, targetMethod, true);
      String afterCode = buildCodeBlock(map.get(CustomizeRulePosition.AFTER.name()), classPool, targetClass, targetMethod, false);
      if (replaceCode != null)
        targetMethod.setBody(replaceCode); 
      if (beforeCode != null)
        targetMethod.insertBefore(beforeCode); 
      if (afterCode != null)
        targetMethod.insertAfter(afterCode); 
      HotSwapClient.redefine(Class.forName(targetClassName), targetClass);
    } catch (NotFoundException|ClassNotFoundException e) {
      throw new IllegalArgumentException("error.ruleCode.classNotFound", e);
    } catch (CannotCompileException e) {
      throw new IllegalArgumentException("error.ruleCode.compileFail", e);
    } catch (IOException e) {
      throw new CommonException("error.ruleCode.readError", e);
    } catch (VMNotFoundException e) {
      throw new CommonException("customize.compile.notEnableDynamicCompile", e);
    } catch (Exception e) {
      LOGGER.error("customize error. ex={}", e.getMessage());
      throw e;
    } 
  }
  
  private CtClass[] buildParams(ClassPool classPool, ApiCustomizePoint point) throws NotFoundException {
    CtClass[] params = null;
    if (StringUtils.isNotBlank(point.getMethodArgs()) && !"none".equalsIgnoreCase(point.getMethodArgs())) {
      String[] arr = point.getMethodArgs().split(",");
      params = new CtClass[arr.length];
      for (int i = 0; i < arr.length; i++)
        params[i] = classPool.get(arr[i]); 
    } 
    return params;
  }
  
  private String buildCodeBlock(List<ApiCustomizeRule> rules, ClassPool classPool, CtClass targetClass, CtMethod targetMethod, boolean returnValue) {
    if (CollectionUtils.isEmpty(rules))
      return null; 
    StringBuilder builder = new StringBuilder();
    builder.append("{").append("\r\n");
    try {
      for (ApiCustomizeRule rule : rules) {
        String code = getCodeGenerator().generate(classPool, targetClass, targetMethod, rule, returnValue);
        if (StringUtils.isNotBlank(code))
          builder.append(code); 
      } 
    } catch (Exception e) {
      LOGGER.error("build code block failed");
      throw new IllegalArgumentException("use CodeGenerator generate code failed", e);
    } 
    builder.append("}").append("\r\n");
    LOGGER.info("-------------------------------- beign: generated code block ---------------------------------------\r\n{}", builder.toString());
    LOGGER.info("-------------------------------- end: generated code block ---------------------------------------");
    return builder.toString();
  }
}
